/* eslint-disable */

/**
 * ============================================
 * models.js
 */

const xhr_timeout = 15000;

/**
 *
 * @param {*} file
 * @param {*} hash
 */
var FileObject = function(file, hash) {
  this.name = encodeURIComponent(file.name);
  this.hash = hash;
  this.size = file.size;
  this.fkey = hash + "@" + this.name.replace(/@/g, "_");
};

/**
 *
 * @param {*} fileobj
 * @param {*} index
 * @param {*} count
 * @param {*} start
 * @param {*} end
 * @param {*} length
 * @param {*} hash
 */
var ChunkObject = function(fileobj, index, count, start, end, length, hash) {
  this.fkey = fileobj.fkey;
  this.index = index;
  this.count = count;
  this.start = start;
  this.end = end;
  this.length = length;
  this.hash = hash;
};

/**
 * ============================================
 * ajax.js
 */
/**
 * 获取上传文件上下文许可
 * @param {*} url
 * @param {*} fileObject
 * @param {*} headers
 * @param {*} onload
 * @param {*} onerror
 */
function makeFile(url, fileObject, headers, onload, onerror) {
  let xhr = new XMLHttpRequest(),
    fkey = fileObject.fkey;
  url += "/api/upload/mkfile/" + fkey;
  xhr.open("post", url, false);
  for (let key in headers) {
    xhr.setRequestHeader(key, headers[key]);
  }
  xhr.timeout = xhr_timeout * 0.5;
  xhr.ontimeout = () => {
    onerror(new Error("time out"));
  };
  xhr.onload = (e) => {
    onload(xhr.response);
  };
  xhr.onerror = (e) => {
    onerror(e);
  };
  try {
    xhr.send();
  } catch (e) {
    console.error(e);
  }
}

/**
 * 文件完整性检查
 * @param {*} url
 * @param {*} chunkObject
 * @param {*} headers
 * @param {*} onload
 * @param {*} onerror
 */
function checkFileCompleted(url, chunkObject, headers, onload, onerror) {
  let xhr = new XMLHttpRequest(),
    fkey = chunkObject.fkey,
    count = chunkObject.count;
  url += "/api/upload/chkfile/" + count + "/fkey/" + fkey;
  xhr.open("post", url, false);
  for (let key in headers) {
    xhr.setRequestHeader(key, headers[key]);
  }
  xhr.timeout = xhr_timeout * 0.5;
  xhr.ontimeout = () => {
    onerror(new Error("time out"));
  };
  xhr.onload = (e) => {
    onload(xhr.response);
  };
  xhr.onerror = (e) => {
    onerror(e);
  };
  try {
    xhr.send();
  } catch (e) {
    console.error(e);
  }
}

/**
 * 块hash效验
 * @description api/upload/chkchunk/{fkey}/index/{index}/hash/{hash}
 * @param {*} url
 * @param {*} chunkObject
 * @param {*} headers
 * @param {*} onload
 * @param {*} onerror
 */
function checkChunk(url, chunkObject, headers, onload, onerror) {
  var xhr = new XMLHttpRequest(),
    fkey = chunkObject.fkey,
    index = chunkObject.index,
    hash = chunkObject.hash;
  url += "/api/upload/chkchunk/" + fkey + "/index/" + index + "/hash/" + hash;
  xhr.open("get", url);
  for (let key in headers) {
    xhr.setRequestHeader(key, headers[key]);
  }
  xhr.timeout = xhr_timeout * 0.5;
  xhr.ontimeout = () => {
    onerror(new Error("time out"));
  };
  xhr.onload = (e) => {
    onload(xhr.response);
  };
  xhr.onerror = (e) => {
    onerror(e);
  };
  try {
    xhr.send();
  } catch (e) {}
}

/**
 * ArrayBuffer上传
 * @description api/upload/putchunk/{index}/hash/{hash}
 * @param {*} url
 * @param {*} buffer
 * @param {*} headers
 * @param {*} onload
 * @param {*} onprogress
 * @param {*} onerror
 */
function uploadStream(
  url,
  buffer,
  chunkObject,
  headers,
  onload,
  onprogress,
  onerror
) {
  let xhr = new XMLHttpRequest(),
    fkey = chunkObject.fkey,
    index = chunkObject.index,
    hash = chunkObject.hash,
    form = new FormData();
  form.append("file", new File([buffer], index));
  url += "/api/upload/putchunk/" + fkey + "/index/" + index + "/hash/" + hash;
  //https://developer.mozilla.org/zh-CN/docs/Web/API/XMLHttpRequest/open
  xhr.open("post", url); //if ArrayBuffer,here must be false
  for (let key in headers) {
    xhr.setRequestHeader(key, headers[key]);
  }
  //xhr.responseType = "json";
  xhr.timeout = xhr_timeout;
  xhr.ontimeout = () => {
    onerror(new Error("time out"));
  };
  xhr.onload = (e) => {
    onload(xhr.response);
  };
  xhr.onerror = (e) => {
    onerror(e);
  };
  xhr.upload.onprogress = (e) => {
    onprogress(e);
  };
  try {
    xhr.send(form);
  } catch (e) {
    console.error(e);
  }
}

/**
 * 文件上传
 * @description api/upload/file/{fkey}
 * @param {*} url
 * @param {*} fileObject
 * @param {*} headers
 * @param {*} onload
 * @param {*} onprogress
 * @param {*} onerror
 */
function uploadFile(url, fileObject, headers, onload, onprogress, onerror) {
  var form = new FormData(),
    fkey = fileObject.name;
  form.append("file", fileObject.file);
  url += "/api/upload/file/" + fkey;
  var xhr = new XMLHttpRequest();
  xhr.open("post", url, true); //if multipart,here is must be true
  for (let key in headers) {
    xhr.setRequestHeader(key, headers[key]);
  }
  xhr.timeout = xhr_timeout * 2;
  xhr.ontimeout = () => {
    onerror(new Error("time out"));
  };
  xhr.onload = (e) => {
    onload(xhr.response);
  };
  xhr.onerror = (e) => {
    onerror(e);
  };
  xhr.upload.onprogress = (e) => {
    onprogress(e);
  };
  try {
    xhr.send(form);
  } catch (e) {
    console.error(e);
  }
}

/**
 * 文件直接下载
 * @description api/file/{fkey}
 * @param {*} url
 * @param {*} fkey
 */
function downloadFile(url, fkey, onload, onerror) {
  var xhr = new XMLHttpRequest();
  url += "/api/download/file/" + fkey;
  xhr.open("get", url, true);
  for (let key in headers) {
    xhr.setRequestHeader(key, headers[key]);
  }
  xhr.timeout = xhr_timeout * 4;
  xhr.ontimeout = () => {
    onerror(new Error("time out"));
  };
  xhr.onload = onload;
  xhr.onerror = onerror;
  xhr.send();
}

/**
 * 文件片信息
 * @description api/file/chunks-indexes/{fkey}
 * @param {*} url
 * @param {*} fkey
 */
function getAllChunks(url, fkey, onload, onerror) {
  var xhr = new XMLHttpRequest();
  url += "/api/download/file/chunks-indexes/" + fkey;
  xhr.open("get", url, true);
  for (let key in headers) {
    xhr.setRequestHeader(key, headers[key]);
  }
  xhr.timeout = xhr_timeout * 2;
  xhr.ontimeout = () => {
    onerror(new Error("time out"));
  };
  xhr.onload = (e) => {
    onload(xhr.response);
  };
  xhr.onerror = (e) => {
    onerror(e);
  };
  xhr.send();
}

/**
 * 片下载
 * @description api/file/chunks/{fkey}/{index}
 * @param {*} url
 * @param {*} fkey
 * @param {*} index
 */
function downloadBuffer(url, fkey, index) {
  var xhr = new XMLHttpRequest();
  url += "/api/download/file/chunks/" + fkey + "/" + index;
  xhr.open("get", url, true);
  for (let key in headers) {
    xhr.setRequestHeader(key, headers[key]);
  }
  xhr.responseType = "arraybuffer";
  xhr.timeout = xhr_timeout * 2;
  xhr.ontimeout = () => {
    onerror(new Error("time out"));
  };
  xhr.onload = (e) => {
    onload(xhr.response);
  };
  xhr.onerror = (e) => {
    onerror(e);
  };
  xhr.send();
}

/**
 * ============================================
 * sha1.js
 */

/*
 * [js-sha1]{@link https://github.com/emn178/js-sha1}
 *
 * @version 0.6.0
 * @author Chen, Yi-Cyuan [emn178@gmail.com]
 * @copyright Chen, Yi-Cyuan 2014-2017
 * @license MIT
 */
/*jslint bitwise: true */
var ERROR = "input is invalid type";
var WINDOW = typeof window === "object";
var root = WINDOW ? window : {};
if (root.JS_SHA1_NO_COMMON_JS) {
  WINDOW = false;
}
var WEB_WORKER = !WINDOW && typeof self === "object";
var NODE_JS =
  !root.JS_SHA1_NO_COMMON_JS &&
  typeof process === "object" &&
  process.versions &&
  process.versions.node;
if (NODE_JS) {
  root = global;
} else if (WEB_WORKER) {
  root = self;
}
var COMMON_JS =
  !root.JS_SHA1_NO_COMMON_JS && typeof module === "object" && module.exports;
var AMD = typeof define === "function" && define.amd;

var HEX_CHARS = "0123456789abcdef".split("");
var EXTRA = [-2147483648, 8388608, 32768, 128];
var SHIFT = [24, 16, 8, 0];
var OUTPUT_TYPES = ["hex", "array", "digest", "arrayBuffer"];

var blocks = [];

var createOutputMethod = function(outputType) {
  return function(message) {
    return new Sha1(true).update(message)[outputType]();
  };
};

var createMethod = function() {
  var method = createOutputMethod("hex");
  if (NODE_JS) {
    method = nodeWrap(method);
  }
  method.create = function() {
    return new Sha1();
  };
  method.update = function(message) {
    return method.create().update(message);
  };
  for (var i = 0; i < OUTPUT_TYPES.length; ++i) {
    var type = OUTPUT_TYPES[i];
    method[type] = createOutputMethod(type);
  }
  return method;
};

var nodeWrap = function(method) {
  var crypto = eval("require('crypto')");
  var Buffer = eval("require('buffer').Buffer");
  var nodeMethod = function(message) {
    if (typeof message === "string") {
      return crypto
        .createHash("sha1")
        .update(message, "utf8")
        .digest("hex");
    } else if (message.constructor === ArrayBuffer) {
      message = new Uint8Array(message);
    } else if (message.length === undefined) {
      return method(message);
    }
    return crypto
      .createHash("sha1")
      .update(new Buffer(message))
      .digest("hex");
  };
  return nodeMethod;
};

function Sha1(sharedMemory) {
  if (sharedMemory) {
    blocks[0] = blocks[16] = blocks[1] = blocks[2] = blocks[3] = blocks[4] = blocks[5] = blocks[6] = blocks[7] = blocks[8] = blocks[9] = blocks[10] = blocks[11] = blocks[12] = blocks[13] = blocks[14] = blocks[15] = 0;
    this.blocks = blocks;
  } else {
    this.blocks = [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0];
  }

  this.h0 = 0x67452301;
  this.h1 = 0xefcdab89;
  this.h2 = 0x98badcfe;
  this.h3 = 0x10325476;
  this.h4 = 0xc3d2e1f0;

  this.block = this.start = this.bytes = this.hBytes = 0;
  this.finalized = this.hashed = false;
  this.first = true;
}

Sha1.prototype.update = function(message) {
  if (this.finalized) {
    return;
  }
  var notString = typeof message !== "string";
  if (notString && message.constructor === root.ArrayBuffer) {
    message = new Uint8Array(message);
  }
  var code,
    index = 0,
    i,
    length = message.length || 0,
    blocks = this.blocks;

  while (index < length) {
    if (this.hashed) {
      this.hashed = false;
      blocks[0] = this.block;
      blocks[16] = blocks[1] = blocks[2] = blocks[3] = blocks[4] = blocks[5] = blocks[6] = blocks[7] = blocks[8] = blocks[9] = blocks[10] = blocks[11] = blocks[12] = blocks[13] = blocks[14] = blocks[15] = 0;
    }

    if (notString) {
      for (i = this.start; index < length && i < 64; ++index) {
        blocks[i >> 2] |= message[index] << SHIFT[i++ & 3];
      }
    } else {
      for (i = this.start; index < length && i < 64; ++index) {
        code = message.charCodeAt(index);
        if (code < 0x80) {
          blocks[i >> 2] |= code << SHIFT[i++ & 3];
        } else if (code < 0x800) {
          blocks[i >> 2] |= (0xc0 | (code >> 6)) << SHIFT[i++ & 3];
          blocks[i >> 2] |= (0x80 | (code & 0x3f)) << SHIFT[i++ & 3];
        } else if (code < 0xd800 || code >= 0xe000) {
          blocks[i >> 2] |= (0xe0 | (code >> 12)) << SHIFT[i++ & 3];
          blocks[i >> 2] |= (0x80 | ((code >> 6) & 0x3f)) << SHIFT[i++ & 3];
          blocks[i >> 2] |= (0x80 | (code & 0x3f)) << SHIFT[i++ & 3];
        } else {
          code =
            0x10000 +
            (((code & 0x3ff) << 10) | (message.charCodeAt(++index) & 0x3ff));
          blocks[i >> 2] |= (0xf0 | (code >> 18)) << SHIFT[i++ & 3];
          blocks[i >> 2] |= (0x80 | ((code >> 12) & 0x3f)) << SHIFT[i++ & 3];
          blocks[i >> 2] |= (0x80 | ((code >> 6) & 0x3f)) << SHIFT[i++ & 3];
          blocks[i >> 2] |= (0x80 | (code & 0x3f)) << SHIFT[i++ & 3];
        }
      }
    }

    this.lastByteIndex = i;
    this.bytes += i - this.start;
    if (i >= 64) {
      this.block = blocks[16];
      this.start = i - 64;
      this.hash();
      this.hashed = true;
    } else {
      this.start = i;
    }
  }
  if (this.bytes > 4294967295) {
    this.hBytes += (this.bytes / 4294967296) << 0;
    this.bytes = this.bytes % 4294967296;
  }
  return this;
};

Sha1.prototype.finalize = function() {
  if (this.finalized) {
    return;
  }
  this.finalized = true;
  var blocks = this.blocks,
    i = this.lastByteIndex;
  blocks[16] = this.block;
  blocks[i >> 2] |= EXTRA[i & 3];
  this.block = blocks[16];
  if (i >= 56) {
    if (!this.hashed) {
      this.hash();
    }
    blocks[0] = this.block;
    blocks[16] = blocks[1] = blocks[2] = blocks[3] = blocks[4] = blocks[5] = blocks[6] = blocks[7] = blocks[8] = blocks[9] = blocks[10] = blocks[11] = blocks[12] = blocks[13] = blocks[14] = blocks[15] = 0;
  }
  blocks[14] = (this.hBytes << 3) | (this.bytes >>> 29);
  blocks[15] = this.bytes << 3;
  this.hash();
};

Sha1.prototype.hash = function() {
  var a = this.h0,
    b = this.h1,
    c = this.h2,
    d = this.h3,
    e = this.h4;
  var f,
    j,
    t,
    blocks = this.blocks;

  for (j = 16; j < 80; ++j) {
    t = blocks[j - 3] ^ blocks[j - 8] ^ blocks[j - 14] ^ blocks[j - 16];
    blocks[j] = (t << 1) | (t >>> 31);
  }

  for (j = 0; j < 20; j += 5) {
    f = (b & c) | (~b & d);
    t = (a << 5) | (a >>> 27);
    e = (t + f + e + 1518500249 + blocks[j]) << 0;
    b = (b << 30) | (b >>> 2);

    f = (a & b) | (~a & c);
    t = (e << 5) | (e >>> 27);
    d = (t + f + d + 1518500249 + blocks[j + 1]) << 0;
    a = (a << 30) | (a >>> 2);

    f = (e & a) | (~e & b);
    t = (d << 5) | (d >>> 27);
    c = (t + f + c + 1518500249 + blocks[j + 2]) << 0;
    e = (e << 30) | (e >>> 2);

    f = (d & e) | (~d & a);
    t = (c << 5) | (c >>> 27);
    b = (t + f + b + 1518500249 + blocks[j + 3]) << 0;
    d = (d << 30) | (d >>> 2);

    f = (c & d) | (~c & e);
    t = (b << 5) | (b >>> 27);
    a = (t + f + a + 1518500249 + blocks[j + 4]) << 0;
    c = (c << 30) | (c >>> 2);
  }

  for (; j < 40; j += 5) {
    f = b ^ c ^ d;
    t = (a << 5) | (a >>> 27);
    e = (t + f + e + 1859775393 + blocks[j]) << 0;
    b = (b << 30) | (b >>> 2);

    f = a ^ b ^ c;
    t = (e << 5) | (e >>> 27);
    d = (t + f + d + 1859775393 + blocks[j + 1]) << 0;
    a = (a << 30) | (a >>> 2);

    f = e ^ a ^ b;
    t = (d << 5) | (d >>> 27);
    c = (t + f + c + 1859775393 + blocks[j + 2]) << 0;
    e = (e << 30) | (e >>> 2);

    f = d ^ e ^ a;
    t = (c << 5) | (c >>> 27);
    b = (t + f + b + 1859775393 + blocks[j + 3]) << 0;
    d = (d << 30) | (d >>> 2);

    f = c ^ d ^ e;
    t = (b << 5) | (b >>> 27);
    a = (t + f + a + 1859775393 + blocks[j + 4]) << 0;
    c = (c << 30) | (c >>> 2);
  }

  for (; j < 60; j += 5) {
    f = (b & c) | (b & d) | (c & d);
    t = (a << 5) | (a >>> 27);
    e = (t + f + e - 1894007588 + blocks[j]) << 0;
    b = (b << 30) | (b >>> 2);

    f = (a & b) | (a & c) | (b & c);
    t = (e << 5) | (e >>> 27);
    d = (t + f + d - 1894007588 + blocks[j + 1]) << 0;
    a = (a << 30) | (a >>> 2);

    f = (e & a) | (e & b) | (a & b);
    t = (d << 5) | (d >>> 27);
    c = (t + f + c - 1894007588 + blocks[j + 2]) << 0;
    e = (e << 30) | (e >>> 2);

    f = (d & e) | (d & a) | (e & a);
    t = (c << 5) | (c >>> 27);
    b = (t + f + b - 1894007588 + blocks[j + 3]) << 0;
    d = (d << 30) | (d >>> 2);

    f = (c & d) | (c & e) | (d & e);
    t = (b << 5) | (b >>> 27);
    a = (t + f + a - 1894007588 + blocks[j + 4]) << 0;
    c = (c << 30) | (c >>> 2);
  }

  for (; j < 80; j += 5) {
    f = b ^ c ^ d;
    t = (a << 5) | (a >>> 27);
    e = (t + f + e - 899497514 + blocks[j]) << 0;
    b = (b << 30) | (b >>> 2);

    f = a ^ b ^ c;
    t = (e << 5) | (e >>> 27);
    d = (t + f + d - 899497514 + blocks[j + 1]) << 0;
    a = (a << 30) | (a >>> 2);

    f = e ^ a ^ b;
    t = (d << 5) | (d >>> 27);
    c = (t + f + c - 899497514 + blocks[j + 2]) << 0;
    e = (e << 30) | (e >>> 2);

    f = d ^ e ^ a;
    t = (c << 5) | (c >>> 27);
    b = (t + f + b - 899497514 + blocks[j + 3]) << 0;
    d = (d << 30) | (d >>> 2);

    f = c ^ d ^ e;
    t = (b << 5) | (b >>> 27);
    a = (t + f + a - 899497514 + blocks[j + 4]) << 0;
    c = (c << 30) | (c >>> 2);
  }

  this.h0 = (this.h0 + a) << 0;
  this.h1 = (this.h1 + b) << 0;
  this.h2 = (this.h2 + c) << 0;
  this.h3 = (this.h3 + d) << 0;
  this.h4 = (this.h4 + e) << 0;
};

Sha1.prototype.hex = function() {
  this.finalize();

  var h0 = this.h0,
    h1 = this.h1,
    h2 = this.h2,
    h3 = this.h3,
    h4 = this.h4;

  return (
    HEX_CHARS[(h0 >> 28) & 0x0f] +
    HEX_CHARS[(h0 >> 24) & 0x0f] +
    HEX_CHARS[(h0 >> 20) & 0x0f] +
    HEX_CHARS[(h0 >> 16) & 0x0f] +
    HEX_CHARS[(h0 >> 12) & 0x0f] +
    HEX_CHARS[(h0 >> 8) & 0x0f] +
    HEX_CHARS[(h0 >> 4) & 0x0f] +
    HEX_CHARS[h0 & 0x0f] +
    HEX_CHARS[(h1 >> 28) & 0x0f] +
    HEX_CHARS[(h1 >> 24) & 0x0f] +
    HEX_CHARS[(h1 >> 20) & 0x0f] +
    HEX_CHARS[(h1 >> 16) & 0x0f] +
    HEX_CHARS[(h1 >> 12) & 0x0f] +
    HEX_CHARS[(h1 >> 8) & 0x0f] +
    HEX_CHARS[(h1 >> 4) & 0x0f] +
    HEX_CHARS[h1 & 0x0f] +
    HEX_CHARS[(h2 >> 28) & 0x0f] +
    HEX_CHARS[(h2 >> 24) & 0x0f] +
    HEX_CHARS[(h2 >> 20) & 0x0f] +
    HEX_CHARS[(h2 >> 16) & 0x0f] +
    HEX_CHARS[(h2 >> 12) & 0x0f] +
    HEX_CHARS[(h2 >> 8) & 0x0f] +
    HEX_CHARS[(h2 >> 4) & 0x0f] +
    HEX_CHARS[h2 & 0x0f] +
    HEX_CHARS[(h3 >> 28) & 0x0f] +
    HEX_CHARS[(h3 >> 24) & 0x0f] +
    HEX_CHARS[(h3 >> 20) & 0x0f] +
    HEX_CHARS[(h3 >> 16) & 0x0f] +
    HEX_CHARS[(h3 >> 12) & 0x0f] +
    HEX_CHARS[(h3 >> 8) & 0x0f] +
    HEX_CHARS[(h3 >> 4) & 0x0f] +
    HEX_CHARS[h3 & 0x0f] +
    HEX_CHARS[(h4 >> 28) & 0x0f] +
    HEX_CHARS[(h4 >> 24) & 0x0f] +
    HEX_CHARS[(h4 >> 20) & 0x0f] +
    HEX_CHARS[(h4 >> 16) & 0x0f] +
    HEX_CHARS[(h4 >> 12) & 0x0f] +
    HEX_CHARS[(h4 >> 8) & 0x0f] +
    HEX_CHARS[(h4 >> 4) & 0x0f] +
    HEX_CHARS[h4 & 0x0f]
  );
};

Sha1.prototype.toString = Sha1.prototype.hex;

Sha1.prototype.digest = function() {
  this.finalize();

  var h0 = this.h0,
    h1 = this.h1,
    h2 = this.h2,
    h3 = this.h3,
    h4 = this.h4;

  return [
    (h0 >> 24) & 0xff,
    (h0 >> 16) & 0xff,
    (h0 >> 8) & 0xff,
    h0 & 0xff,
    (h1 >> 24) & 0xff,
    (h1 >> 16) & 0xff,
    (h1 >> 8) & 0xff,
    h1 & 0xff,
    (h2 >> 24) & 0xff,
    (h2 >> 16) & 0xff,
    (h2 >> 8) & 0xff,
    h2 & 0xff,
    (h3 >> 24) & 0xff,
    (h3 >> 16) & 0xff,
    (h3 >> 8) & 0xff,
    h3 & 0xff,
    (h4 >> 24) & 0xff,
    (h4 >> 16) & 0xff,
    (h4 >> 8) & 0xff,
    h4 & 0xff,
  ];
};

Sha1.prototype.array = Sha1.prototype.digest;

Sha1.prototype.arrayBuffer = function() {
  this.finalize();

  var buffer = new ArrayBuffer(20);
  var dataView = new DataView(buffer);
  dataView.setUint32(0, this.h0);
  dataView.setUint32(4, this.h1);
  dataView.setUint32(8, this.h2);
  dataView.setUint32(12, this.h3);
  dataView.setUint32(16, this.h4);
  return buffer;
};

var exports = createMethod();

if (COMMON_JS) {
  module.exports = exports;
} else {
  root.sha1 = exports;
  if (AMD) {
    define(function() {
      return exports;
    });
  }
}

/**
 * ============================================
 * qetag.js
 */

function getFileSha1(file, cb, pgcb, sliceSize) {
  // sha1算法
  var sha1 = createMethod();
  var shA1 = sha1.digest,
    prefix = 0x16,
    blockCount = 0,
    sha1String = [],
    blockfile = undefined;
  var blockSize = sliceSize | (4 * 1024 * 1024);

  if (file) {
    let totalsize = file.size;
    blockCount = Math.ceil(totalsize / blockSize);
    //let starttime = Date.now();
    let step =
      blockCount < 21
        ? 1
        : blockCount > 100
        ? blockCount > 500
          ? blockCount > 1000
            ? 23
            : 11
          : 7
        : 3;
    let hashindex = [];
    for (let i = 0; i < blockCount - 1; i += step) {
      hashindex.push(i);
    }
    hashindex.push(blockCount - 1);

    for (let i in hashindex) {
      let reader = new FileReader();
      reader.id = i;
      reader.name = file.name;
      reader.count = hashindex.length;
      reader.start = 0;
      reader.startbt = 0;
      blockfile = file.slice(
        hashindex[i] * blockSize,
        (hashindex[i] + 1) * blockSize
      );
      reader.onload = (e) => {
        let buffer = e.currentTarget.result;
        sha1String[i] = shA1(buffer);
        if (Object.getOwnPropertyNames(sha1String).length > hashindex.length) {
          if (cb) {
            cb(calcEtag());
          }
        }
      };
      reader.readAsArrayBuffer(blockfile);
      reader.onloadend = (e) => {
        if (pgcb) {
          pgcb(e);
        }
      };
      reader.onprogress = progress;
    }
  }

  function progress(evt) {
    if (evt.lengthComputable) {
      //let percentLoaded = (evt.loaded / evt.total) * 100;
      let reader = evt.currentTarget;
      //let speed = (evt.loaded - reader.startbt) / (evt.timeStamp - reader.start);
      reader.start = evt.timeStamp;
      reader.startbt = evt.loaded;
      //console.log(reader.id+'/'+reader.count, reader.name, percentLoaded, speed);
    }
  }

  function concatArr2Uint8(s) {
    //Array 2 Uint8Array
    let tmp = [];
    for (let i of s) tmp = tmp.concat(i);
    return new Uint8Array(tmp);
  }

  function Uint8ToBase64(u8Arr, urisafe) {
    //Uint8Array 2 Base64
    let CHUNK_SIZE = 0x8000; //arbitrary number
    let index = 0;
    let length = u8Arr.length;
    let result = "";
    let slice;
    while (index < length) {
      slice = u8Arr.subarray(index, Math.min(index + CHUNK_SIZE, length));
      result += String.fromCharCode.apply(null, slice);
      index += CHUNK_SIZE;
    }
    return urisafe
      ? btoa(result)
          .replace(/\//g, "_")
          .replace(/\+/g, "-")
      : btoa(result);
  }

  function calcEtag() {
    if (!sha1String.length) return "Fto5o-5ea0sNMlW_75VgGJCv2AcJ";
    let sha1Buffer = concatArr2Uint8(sha1String);
    // 如果大于4M，则对各个块的sha1结果再次sha1
    if (blockCount > 1) {
      prefix = 0x96;
      sha1Buffer = shA1(sha1Buffer.buffer);
    } else {
      sha1Buffer = Array.apply([], sha1Buffer);
    }
    sha1Buffer = concatArr2Uint8([[prefix], sha1Buffer]);
    return Uint8ToBase64(sha1Buffer, true);
  }
  return calcEtag();
}

function getChunkSha1(buffer, sliceSize) {
  // sha1算法
  var sha1 = createMethod();
  var shA1 = sha1.digest,
    prefix = 0x16,
    blockCount = 0,
    sha1String = [];
  var blockSize = sliceSize | (4 * 1024 * 1024);

  if (buffer) {
    let bufferSize = buffer.size || buffer.length || buffer.byteLength;
    if (blockSize >= bufferSize) {
      blockCount = 1;
      sha1String.push(shA1(buffer));
    } else {
      blockCount = Math.ceil(bufferSize / blockSize);
      for (let i = 0; i < blockCount; i++) {
        sha1String.push(shA1(buffer.slice(i * blockSize, (i + 1) * blockSize)));
      }
    }
  }

  function concatArr2Uint8(s) {
    //Array 2 Uint8Array
    let tmp = [];
    for (let i of s) tmp = tmp.concat(i);
    return new Uint8Array(tmp);
  }

  function Uint8ToBase64(u8Arr, urisafe) {
    //Uint8Array 2 Base64
    let CHUNK_SIZE = 0x8000; //arbitrary number
    let index = 0;
    let length = u8Arr.length;
    let result = "";
    let slice;
    while (index < length) {
      slice = u8Arr.subarray(index, Math.min(index + CHUNK_SIZE, length));
      result += String.fromCharCode.apply(null, slice);
      index += CHUNK_SIZE;
    }
    return urisafe
      ? btoa(result)
          .replace(/\//g, "_")
          .replace(/\+/g, "-")
      : btoa(result);
  }

  function calcEtag() {
    if (!sha1String.length) return "Fto5o-5ea0sNMlW_75VgGJCv2AcJ";
    let sha1Buffer = concatArr2Uint8(sha1String);
    // 如果大于4M，则对各个块的sha1结果再次sha1
    if (blockCount > 1) {
      prefix = 0x96;
      sha1Buffer = shA1(sha1Buffer.buffer);
    } else {
      sha1Buffer = Array.apply([], sha1Buffer);
    }
    sha1Buffer = concatArr2Uint8([[prefix], sha1Buffer]);
    return Uint8ToBase64(sha1Buffer, true);
  }
  return calcEtag();
}
/**
 * ============================================
 * worker.js
 */

/**
 * 重试机制，保证服务器不可用情况下数据的完整上传
 * 10秒钟超时*20次重试保证200秒内传输延迟
 */

const chunk_delay = 1;
const retry_delay = 100;
const retry_max = 20;
const context_head = "AQEvdGV";
const actions = {
  add: onFileAdd,
  upload: onUpload,
};

self.addEventListener("message", function(evt) {
  var data = evt.data;
  if (actions[data.type]) {
    actions[data.type].call(this, data);
  }
});

/**
 * @funtion onFileAdd
 * onFileAdd - create slice parts from file to send async or
 * whatever to do with this parts of the file
 * @param {*} data
 * @param {*} options
 */
function onFileAdd(item) {
  var file = item.data.data;
  var fileparse = 0;
  try {
    getFileSha1(
      file,
      (hash) => {
        self.postMessage({
          cmd: "parse-complete",
          data: {
            name: file.name,
            hash: hash,
          },
        });
      },
      (evt) => {
        let reader = evt.currentTarget;
        //解析任务
        self.postMessage({
          cmd: "parsed",
          data: {
            id: reader.id,
            name: reader.name,
          },
        });
        fileparse++;
        let percent = Math.round((fileparse / reader.count) * 100);
        //解析进度
        self.postMessage({
          cmd: "loaded",
          data: {
            name: reader.name,
            percent: percent,
          },
        });
      }
    );
    return;
  } catch (e) {
    self.postMessage({
      cmd: "worker-error",
      data: e,
    });
    self.close();
  }
  self.close();
}

function onUpload(item) {
  var file = item.data.data;
  var hash = item.data.hash;
  var options = item.options;
  var baseUrl = options.baseUrl || "http://localhost:8080";
  let chunkSize = options.chunkSize;
  var chunkloaded = 0;
  var chunksProgress = [];
  var retry_times = [];
  var global_headers = options.headers || {};
  let fileobj = new FileObject(file, hash);
  //增加验证头信息
  let chunkPoints = getChunkPoints(fileobj, chunkSize);
  let chunkCount = chunkPoints.length - 1;
  let upcontext = "";
  try {
    //创建文件，准备文件目录
    let file_headers = Object.assign(
      {
        "Up-Length": file.size,
      },
      global_headers
    );
    makeFile(
      baseUrl,
      fileobj,
      file_headers,
      (res) => {
        //获取上传许可的上下文
        if (res.indexOf(context_head) != -1) {
          upcontext = res;
          for (let i = 0; i < chunkCount; i++) {
            let _start = chunkPoints[i],
              _end = chunkPoints[i + 1];
            //切块
            let _slice = file.slice(_start, _end);
            //初始化块进度
            chunksProgress[i] = {
              id: i,
              name: fileobj.name,
              percent: 0,
            };
            //构造片对象
            let _chunkobj = new ChunkObject(
              fileobj,
              i,
              chunkCount,
              _start,
              _end,
              null,
              null
            );
            retry_times[i] = 0;
            setTimeout(
              sendChunk(baseUrl, _slice, _chunkobj, () => {
                chunkloaded++;
                //已完成上传数量计算百分比
                if (chunkloaded >= chunkCount) {
                  //TODO 触发文件完整性检查->集群同步->后端文件服务同步
                  checkFileCompleted(
                    baseUrl,
                    _chunkobj,
                    global_headers,
                    (res) => {                     
                      //发送文件完成消息
                      self.postMessage({
                        cmd: "complete",
                        data: {
                          name: file.name,
                          size: file.size,
                        },
                      });
                      self.close();
                    },
                    (err) => {
                      self.postMessage({
                        cmd: "check-file-error",
                        data: {
                          name: fileobj.name,
                        },
                      });
                      self.close();
                    }
                  );
                } else {
                  let percent = Math.round((chunkloaded / chunkCount) * 100);
                  self.postMessage({
                    cmd: "progress",
                    data: {
                      name: fileobj.name,
                      percent: percent,
                    },
                  });
                }
              }),
              chunk_delay
            );
          }
        } else if (res == "false") {
          //文件秒传
          self.postMessage({
            cmd: "complete",
            data: {
              name: fileobj.name,
              size: fileobj.size,
            },
          });
          self.close();
        } else {
          self.postMessage({
            cmd: "make-file-error",
            data: {
              name: fileobj.name,
            },
          });
        }
      },
      (err) => {
        self.postMessage({
          cmd: "make-file-error",
          data: {
            name: fileobj.name,
          },
        });
      }
    );

    // @function sendChunk
    // Async Simulation
    function sendChunk(baseUrl, slice, chunkobj, _cb) {
      let reader = new FileReader();
      /**
       * 重试传输保证服务器意外情况自动续传
       */
      let retrySendChunk = function() {
        //块发送错误进行一次重试
        retry_times[chunkobj.index] += 1;
        if (retry_times[chunkobj.index] < retry_max) {
          self.postMessage({
            cmd: "upload-chunck-retry",
            data: {
              fkey: chunkobj.fkey,
              index: chunkobj.index,
              retry_times: retry_times[chunkobj.index],
            },
          });
          setTimeout(sendChunk(baseUrl, slice, chunkobj, _cb), retry_delay);
        } else if (retry_times[chunkobj.index] >= retry_max) {
          chunksProgress[chunkobj.index].percent = 0;
          retry_times[chunkobj.index] = 0;
          self.postMessage({
            cmd: "upload-chunck-error",
            data: {
              fkey: chunkobj.fkey,
              index: chunkobj.index,
            },
          });
        }
      };

      reader.onload = (e) => {
        let buffer = e.currentTarget.result;
        let chunkHash = getChunkSha1(buffer);
        let bufferSize = buffer.size || buffer.length || buffer.byteLength;
        //set length and hash
        chunkobj.length = bufferSize;
        chunkobj.hash = chunkHash;
        //create chunk header
        let chunk_headers = Object.assign(
          {
            "Up-Context": upcontext,
            Range: chunkobj.start + "-" + chunkobj.end,
          },
          global_headers
        );
        //块秒传检测
        setTimeout(
          checkChunk(
            baseUrl,
            chunkobj,
            chunk_headers,
            (res) => {
              if (res == "true") {
                //ajax post chunk
                setTimeout(
                  uploadStream(
                    baseUrl,
                    buffer,
                    chunkobj,
                    chunk_headers,
                    (res) => {
                      //块发送完成
                      chunksProgress[chunkobj.index].percent = 100;
                      _cb();
                    },
                    (evt) => {
                      if (evt.lengthComputable && evt.total > 0) {
                        chunksProgress[chunkobj.index].percent = Math.round(
                          (evt.loaded / evt.total) * 100
                        );
                      }
                      //DOTO 发送所有块进度到主线程
                    },
                    (err) => {
                      //块发送错误进行一次重试
                      retrySendChunk();
                    }
                  ),
                  chunk_delay
                );
              } else if (res == "false") {
                //块秒传
                self.postMessage({
                  cmd: "chunk-exist",
                  data: {
                    fkey: chunkobj.fkey,
                    index: chunkobj.index,
                  },
                });
                chunksProgress[chunkobj.index].percent = 100;
                //DOTO 发送所有块进度到主线程
                _cb();
              } else {
                debugger;
                //其它异常情况
                chunksProgress[chunkobj.index].percent = 0;
                retry_times[chunkobj.index] = 0;
                self.postMessage({
                  cmd: "upload-chunck-error",
                  data: {
                    fkey: chunkobj.fkey,
                    index: chunkobj.index,
                  },
                });
              }
            },
            (err) => {
              retrySendChunk();
            }
          ),
          chunk_delay
        );
      };
      reader.readAsArrayBuffer(slice);
    }
    return;
  } catch (e) {
    self.postMessage({
      cmd: "worker-error",
      data: e,
    });
    self.close();
  }
  self.close();
}

function getChunkPoints(fileobj, chunkSize) {
  let chunkPoints = [];
  let total_offset = 0;
  let TOTAL_SIZE = fileobj.size;
  let CHUNK_SIZE = chunkSize; // 4MB
  let chunkCount = Math.ceil(TOTAL_SIZE / CHUNK_SIZE);
  chunkPoints.push(0);
  for (let i = 0; i < chunkCount; i++) {
    total_offset += CHUNK_SIZE;
    if (total_offset > TOTAL_SIZE) total_offset = TOTAL_SIZE;
    chunkPoints.push(total_offset);
  }
  return chunkPoints;
}
